
可变参数模板，已经在4.1节中介绍过了，是包含至少一个模板参数包的模板(参见12.2.4节)。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}可变参数这个术语是从C的可变参数函数借鉴来的，可变参数函数接受任意数量的函数参数。可变参数模板还借鉴了C语言中使用省略号来表示零个或多个参数，并在某些应用程序中作为C语言可变参数函数的(类型)安全替代。
\end{tcolorbox}

当模板的行为可泛化为任意数量的参数时，可变参数模板是有用的。在12.2.4节中介绍的Tuple类模板就是这样一种类型，因为Tuple可以有任意个元素，所有元素的处理方式都是相同的。还可以看下print()函数，其也能接受任意数量的参数，并按顺序显示每个参数。

当为可变参数模板确定模板参数时，可变参数模板中的每个模板参数包将匹配一个由零个或多个模板参数组成的序列，我们将这个模板参数序列称为参数包。下面的例子说明了模板形参包Types，如何为Tuple提供的模板参数匹配不同的参数包:

\begin{lstlisting}[style=styleCXX]
template<typename... Types>
class Tuple {
	public:
	static constexpr std::size_t length = sizeof...(Types);
};

int a1[Tuple<int>::length]; // array of one integer
int a3[Tuple<short, int, long>::length]; // array of three integers
\end{lstlisting}

\subsubsubsection{12.4.1\hspace{0.2cm}包扩展}

sizeof…表达式是包扩展的一个例子，是将参数包展开为独立参数的构造。虽然sizeof…执行这种扩展只是为了计算单独参数的数量，其他形式的参数包(即C++所期望的列表形式)可以扩展为该列表中的多个元素。此类包扩展由列表中元素右侧的省略号(…)标识。下面是一个简单的例子，创建了一个新的类模板MyTuple，派生自Tuple，并传递相应参数:

\begin{lstlisting}[style=styleCXX]
template<typename... Types>
class MyTuple : public Tuple<Types...> {
	// extra operations provided only for MyTuple
};

MyTuple<int, float> t2; // inherits from Tuple<int, float>
\end{lstlisting}

模板参数Types…是一个包扩展，生成一个模板参数序列，在替换为Types的参数包中，每个参数对应一个参数。如示例所示，类型MyTuple<int，float>的实例化实参包int, float替换模板类型参数包Types。当这发生在包扩展Types…时，得到一个用于int的模板参数和一个用于float的模板参数，因此MyTuple<int, floa>继承自Tuple<int, float>。

理解包扩展的一种直观方法是将其视为语法扩展，其中模板参数包替换为相应数量的(非包)模板参数，而包扩展则作为单独的参数列出。例如，将MyTuple展开为两个参数:

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}这种对包扩展的语法理解是一个有用的工具，但是当模板参数包的长度为0时，就失效了。12.4.5节提供了关于零长度包扩展解释的更多细节。
\end{tcolorbox}

\begin{lstlisting}[style=styleCXX]
template<typename T1, typename T2>
class MyTuple : public Tuple<T1, T2> {
	// extra operations provided only for MyTuple
};
\end{lstlisting}

对于三个参数:

\begin{lstlisting}[style=styleCXX]
template<typename T1, typename T2, typename T3>
class MyTuple : public Tuple<T1, T2, T3> {
	// extra operations provided only for MyTuple
};
\end{lstlisting}

不能直接通过名称访问参数包中的单个元素，因为诸如T1、T2等名称在可变参数模板中没有定义。若需要这些类型，惟一能做的就是将它们(递归地)传递给另一个类或函数。

每个包展开都有一个模式，该模式将针对参数包中的每个参数重复的类型或表达式，通常出现在表示包展开的省略号之前。之前的例子只有一些简单的模式(参数包的名称)，但模式的复杂度可以调整。可以定义一个新类型PtrTuple，派生自指向其参数类型的指针元组:

\begin{lstlisting}[style=styleCXX]
template<typename... Types>
class PtrTuple : public Tuple<Types*...> {
	// extra operations provided only for PtrTuple
};

PtrTuple<int, float> t3; // Inherits from Tuple<int*, float*>
\end{lstlisting}

包扩展的模式Types*…在上面的例子中是Types*。对这个模式的重复替换会产生一系列模板类型参数，所有这些参数都是相应类型的指针。在包扩展的语法解释下，若使用三个参数扩展PtrTuple:

\begin{lstlisting}[style=styleCXX]
template<typename T1, typename T2, typename T3>
class PtrTuple : public Tuple<T1*, T2*, T3*> {
	// extra operations provided only for PtrTuple
};
\end{lstlisting}

\subsubsubsection{12.4.2\hspace{0.2cm}何处进行包扩展?}

目前的示例都集中在，使用包扩展来生成一系列模板参数。事实上，包扩展可以在语法中提供逗号分隔列表的地方使用:

\begin{itemize}
\item 
基类列表中。

\item 
构造函数中的基类初始化式列表中。

\item 
调用参数列表中(模式是参数表达式)。

\item 
初始化式列表中(例如，带括号的初始化式列表中)。

\item 
类、函数或别名模板的模板参数列表中。

\item 
函数可能抛出的异常列表中(在C++11和C++14中弃用，在C++17中不允许使用)。

\item 
属性中，如果属性本身支持包扩展(尽管C++没有定义这样的属性)。

\item 
指定声明的对齐时。

\item 
指定Lambda的捕获列表时。

\item 
函数类型的参数列表中。

\item 
使用声明(自C++17;参见4.4.5节)。
\end{itemize}

已经提到了sizeof…作为一种包扩展机制，不会产生列表。C++17还添加了折叠表达式，这是另一种不会产生逗号分隔的机制(参见第12.4.6节)。

其中一些包扩展上下文仅仅为了完整性而包含，因此可以只关注那些在实践中可能有用的包扩展上下文。由于所有上下文中的包扩展都应遵循相同的原则和语法，因此若发现需要更深的包扩展上下文，能够从这里给出的示例进行推导。

基类列表中的包扩展扩展到一些直接基类。这样的扩展可以通过mixin聚合外部提供的数据和功能，mixin是打算“混合”到一个类层次结构中的类，以提供新的行为。例如，下面的Point类在几种不同的上下文中使用包扩展，从而使用任意数量的mixin:

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}mixin将在21.3节中详细讨论。
\end{tcolorbox}

\begin{lstlisting}[style=styleCXX]
template<typename... Mixins>
class Point : public Mixins... { // base class pack expansion
	double x, y, z;
public:
	Point() : Mixins()... { } // base class initializer pack expansion
	
	template<typename Visitor>
	void visitMixins(Visitor visitor) {
		visitor(static_cast<Mixins&>(*this)...); // call argument pack expansion
	}
};

struct Color { char red, green, blue; };
struct Label { std::string name; };
Point<Color, Label> p; // inherits from both Color and Label
\end{lstlisting}

Point类使用包扩展来获取每个mixin，并将其扩展为一个公共基类。然后，Point的默认构造函数在基类初始化器列表中使用包展开，通过mixin机制，对每个基类进行值初始化。

成员函数模板visitMixins是最有趣的，因为它使用包展开的结果作为调用的参数。通过将*this强制转换为mixin类型，包扩展产生的调用参数指向与mixin对应的每个基类。实际上，可以编写访问器来使用visitMixins，使用任意数量的函数调用参数，这在12.4.3节中有介绍。

包扩展也可以在模板参数列表中使用，以创建非类型参数包或模板参数包:

\begin{lstlisting}[style=styleCXX]
template<typename... Ts>
struct Values {
	template<Ts... Vs>
	struct Holder {
	};
};

int i;
Values<char, int, int*>::Holder<’a’, 17, &i> valueHolder;
\end{lstlisting}

当Values<…>已指定，则Values<…>::Holder非类型参数列表长度也就固定了;因此参数包Vs不是一个变长参数包。

Vs是一个非类型模板参数包，其每个实际模板参数都可以具有不同的类型，由模板类型参数包Ts指定类型。Vs声明中的省略号起着双重作用，既可以将模板参数声明为模板参数包，又可以将模板参数包的类型声明为包展开。虽然这样的模板参数包很罕见，但同样的原则适用于更一般的上下文:函数参数。

\subsubsubsection{12.4.3\hspace{0.2cm}函数参数包}

函数参数包是匹配零个或多个函数调用模板参数。与模板参数包一样，函数参数包使用省略号(…)在函数参数名称之前(或位置)，与模板参数包一样，函数参数包在使用时必须使用包展开符来展开。模板参数包和函数参数包可以一并称为参数包。

与模板参数包不同，函数参数包始终是包扩展，因此声明的类型必须至少包含一个参数包。下面的例子中，引入了一个新的Point构造函数，提供的构造函数参数会复制初始化每个mixin:

\begin{lstlisting}[style=styleCXX]
template<typename... Mixins>
class Point : public Mixins...
{
	double x, y, z;
public:
	// default constructor, visitor function, etc. elided
	Point(Mixins... mixins) // mixins is a function parameter pack
		: Mixins(mixins)... { } // initialize each base with the supplied mixin value
};

struct Color { char red, green, blue; };
struct Label { std::string name; };
Point<Color, Label> p({0x7F, 0, 0x7F}, {"center"});
\end{lstlisting}

函数模板的函数参数包，可能依赖于该模板中声明的模板参数包，这允许函数模板接受任意数量的调用参数，而不丢失类型信息:

\begin{lstlisting}[style=styleCXX]
template<typename... Types>
void print(Types... values);

int main()
{
	std::string welcome("Welcome to ");
	print(welcome, "C++ ", 2011, ’\n’); // calls print<std::string, char const*,
} // int, char>
\end{lstlisting}

调用带有参数的函数模板print()时，参数的类型将放在参数包中以替代模板类型的参数包Types，而实际参数值将放在参数包中以替代函数参数包的值。从调用中确定参数的过程将在第15章详细描述。现在，Types中的第i个类型是values中的第i个值的类型，并且这两个参数包都可以在函数模板print()中使用。

print()的实际使用递归模板实例化，这种模板元编程技术在第8.1节和第23章中有描述。

参数列表末尾的未命名函数参数包，和C风格的“vararg”参数之间存在语法上的歧义。例如:

\begin{lstlisting}[style=styleCXX]
template<typename T> void c_style(int, T...);
template<typename... T> void pack(int, T...);
\end{lstlisting}

第一种情况下，“T…”当作“T，…”:未命名的T类型参数，后面跟着一个C风格的可变参数。第二种情况，“T…”视为一个函数参数包，因为T是一个有效的扩展。若要消除歧义可以通过在省略号之前添加逗号(可以确保省略号视为C风格的“vararg”参数)或在…后添加标识符，这可使它成为一个命名函数参数包。在泛型Lambda中，末尾的…如果紧接在它前面的类型(没有中间的逗号)包含auto，则将视为参数包。

\subsubsubsection{12.4.4\hspace{0.2cm}多重和嵌套包扩展}

包扩展的模式的复杂可变，可能包括多个不同的参数包。当实例化包含多个参数包的展开时，所有参数包必须具有相同的长度。通过将每个参数包的第一个参数代入模式，然后代入每个参数包的第二个参数，依此类推，形成类型或值的结果序列。下面的函数在将所有参数转发给函数对象f前，会复制它们:

\begin{lstlisting}[style=styleCXX]
template<typename F, typename... Types>
void forwardCopy(F f, Types const&... values) {
	f(Types(values)...);
}
\end{lstlisting}

调用参数包扩展命名两个参数包，类型和值。实例化此模板时，Types和values参数包的元素级扩展将生成一系列对象构造，这些构造通过在Types中将第i个值转换为第i个类型来构建值中的第i个值的副本。在包扩展的语法解释下，forwardCopy的三个参数如下所示:

\begin{lstlisting}[style=styleCXX]
template<typename F, typename T1, typename T2, typename T3>
void forwardCopy(F f, T1 const& v1, T2 const& v2, T3 const& v3) {
	f(T1(v1), T2(v2), T3(v3));
}
\end{lstlisting}

包扩展也可以嵌套，参数包的每次出现都由其最近的封装包展开来“展开”(且仅是该包展开)。下面的例子演示了包含三个不同参数包的嵌套扩展:

\begin{lstlisting}[style=styleCXX]
template<typename... OuterTypes>
class Nested {
	template<typename... InnerTypes>
	void f(InnerTypes const&... innerValues) {
		g(OuterTypes(InnerTypes(innerValues)...)...);
	}
};
\end{lstlisting}

对g()的调用中，带有模式InnerTypes(innerValues)的包展开是最内层的包展开，同时展开InnerTypes和innerValues，并生成函数调用参数序列，用于初始化由OuterTypes表示的对象。外部包展开的模式包括内部包展开，为函数g()生成一组调用参数，这些参数根据内部展开生成的函数调用参数序列，初始化OuterTypes中的每个类型而创建的。在这个包扩展的语法解释下，OuterTypes有两个参数，InnerTypes和innerValues都有三个参数，嵌套变得更加明显:

\begin{lstlisting}[style=styleCXX]
template<typename O1, typename O2>
class Nested {
	template<typename I1, typename I2, typename I3>
	void f(I1 const& iv1, I2 const& iv2, I3 const& iv3) {
		g(O1(I1(iv1), I2(iv2), I3(iv3)),
		O2(I1(iv1), I2(iv2), I3(iv3)));
	}
};
\end{lstlisting}

多重和嵌套包扩展是一个强大的工具(例如，参见第26.2节)。

\subsubsubsection{12.4.5\hspace{0.2cm}扩展空参数包}

对于理解可变参数模板的实例化如何使用不同数量的参数，包扩展解析是一个有用的工具。然而，在零长度参数包的情况下，语法解析通常会失败。考虑12.4.2节中的Point类模板，在语法上用零参数替换:

\begin{lstlisting}[style=styleCXX]
template<>
class Point : {
	Point() : { }
};
\end{lstlisting}

上面的代码是错误的，因为模板参数列表为空，而空的基类和基类初始化器列表都有一个冒号。

包扩展实际上是语义结构，替换参数包并不影响如何解析包扩展(或其外围的可变参数模板)。当包展开为空列表时，行为(语义上)就好像列表不存在。实例化点<>没有基类，默认构造函数没有基类初始化器，但在其他方面没有问题。即使零长度包扩展的语法解释是定义良好(但不同)的，这种语义规则仍然有效。例如:

\begin{lstlisting}[style=styleCXX]
template<typename T, typename... Types>
void g(Types... values) {
	T v(values...);
}
\end{lstlisting}

可变参数函数模板g()根据给定的值序列创建一个直接初始化的值v。若这个值序列为空，v的声明在语法上看起来就像函数声明T v()。但由于是在语义上对包扩展进行替换，所以不会影响解析产生的实体的类型，可以用零参数初始化v，即值初始化。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}对于类模板的成员和类模板内嵌套类的成员也有类似的限制:若成员声明的类型看起来不是函数类型，实例化后该成员的类型是函数类型，则程序格式错误。因为该成员的语义解释已从数据成员变为成员函数了。
\end{tcolorbox}

\subsubsubsection{12.4.6\hspace{0.2cm}折叠表达式}

编程中重复出现的一种模式是对一系列值进行操作的折叠。例如，函数fn对序列x[1]， x[2]，…，x[n - 1], x[n]的折叠可以表示为fn(x[1], fn(x[2], fn(..., fn(x[n-1], x[n])...)))。

探索一种新的语言特性时，C++委员会遇到了为应用于包扩展的逻辑二进制操作符(即\&\&或||)，从而需要处理这种构造。若没有其他的特性，可以编写以下代码来实现\&\&操作符:

\begin{lstlisting}[style=styleCXX]
bool and_all() { return true; }
template<typename T>
	bool and_all(T cond) { return cond; }
template<typename T, typename... Ts>
	bool and_all(T cond, Ts... conds) {
		return cond && and_all(conds...);
	}
\end{lstlisting}

在C++17中，添加了名为折叠表达式的新特性(参见4.2节的介绍)，适用于除.、\texttt{->}和[]之外的所有二进制操作符。

给定一个未展开的表达式模式包和一个非模式表达式值，C++17也允许为任何此类操作符op编写

\begin{lstlisting}[style=styleCXX]
(pack op ... op value)
\end{lstlisting}

对于右折算子(称为二元右折)，或

\begin{lstlisting}[style=styleCXX]
(value op ... op pack)
\end{lstlisting}

或者是左折(称为二元左折)，这里需要括号。参见4.2节的一些基本示例。

折叠操作适用于展开包并增加值的序列，其要么是序列的最后一个元素(适用于右折叠)，要么是序列的第一个元素(适用于左折叠)。

有了这个特性，代码就可以像这样为每个传递的类型T调用一个特征

\begin{lstlisting}[style=styleCXX]
template<typename... T> bool g() {
	return and_all(trait<T>()...);
}
\end{lstlisting}

(其中and\_all是上面定义的)，可以写成

\begin{lstlisting}[style=styleCXX]
template<typename... T> bool g() {
	return (trait<T>() && ... && true);
}
\end{lstlisting}

折叠表达式是包的扩展。如果包为空，折叠表达式的类型可以根据非包操作数(上面表单中的值)确定。

然而，该特性的设计者还希望有一个选项来省略值操作数。因此，C++17还提供了另外两种形式:一元右折

\begin{lstlisting}[style=styleCXX]
(pack op ... )
\end{lstlisting}

一元左折

\begin{lstlisting}[style=styleCXX]
(... op pack)
\end{lstlisting}

同样，括号是必需的。显然，这给空扩展带来了一个问题:如何确定类型和值?答案是一元折叠的空展开是错误的，有三个例外:

\begin{itemize}
\item
对\&\&的一元折叠的空展开产生的值为true

\item
对||的一元折叠进行空展开将产生值false

\item
逗号操作符(,)的一元折叠的空展开将产生一个空的表达式
\end{itemize}

若以一种不寻常的方式重载这些特殊操作符中的一个，将会产生意外。例如:

\begin{lstlisting}[style=styleCXX]
struct BooleanSymbol {
	...
};

BooleanSymbol operator||(BooleanSymbol, BooleanSymbol);

template<typename... BTs> void symbolic(BTs... ps) {
	BooleanSymbol result = (ps || ...);
	...
}
\end{lstlisting}

假设使用派生自BooleanSymbol的类型来调用symbolic。对于所有展开，结果将产生一个布尔符号值，除了空展开，其将产生一个bool值。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}因为重载这三个特殊操作符很罕见，所以这个问题很少(但很微妙)。最初的折叠表达式建议为更常见的操作符(如+和*)包含空展开值，这将导致更严重的问题。
\end{tcolorbox}

因此，通常会对一元折叠表达式的使用提出警告，并建议使用二元折叠表达式(显式指定空展开的值)。






